﻿using Common;
using HslCommunication.Core;
using PLC.Domain.Dto;
using PLC.Domain.Enum;
using PLC.Domain.Models;
using PLC.Domain.ScheduledTasks;
using PLC.Domain.ScheduledTasks.Strategy.Read;
using PLC.Domain.ScheduledTasks.Strategy.Trigger;
using PLC.Domain.ScheduledTasks.Strategy.Write;
using PLC.Domain.Service.IRepository;
using Serilog;

namespace PLC.Domain.Service.Service;

public class PLCService
{
    // 私有变量
    private IReadWriteNet _plcClient;
    /// <summary>
    /// plc所有读取的参数都在这里
    /// </summary>
    private PLCReadModel  _readModel = new PLCReadModel();
    /// <summary>
    /// 表示已经读取的数据的记录
    /// </summary>
    private List<int> _readGroupList = new List<int>();
    // plc client 创建锁
    private static object _lock = new object(); 
    // 依赖注入
    private readonly IPLCRepository _plcRepository;
    private readonly IEnumerable<BaseRead> _readStrategy;
    private readonly IEnumerable<BaseTrigger> _triggerStrategy;
    private readonly IEnumerable<BaseWrite> _writeStrategy;
    

    public PLCService(IPLCRepository plcRepository,
        IEnumerable<BaseRead> readStrategy,
        IEnumerable<BaseTrigger> triggerStrategy,
        IEnumerable<BaseWrite> writeStrategy)
    {
        _plcRepository = plcRepository?? throw new ArgumentNullException(nameof(plcRepository));
        _readStrategy = readStrategy?? throw new ArgumentNullException(nameof(readStrategy));
        _triggerStrategy = triggerStrategy?? throw new ArgumentNullException(nameof(triggerStrategy));
        _writeStrategy = writeStrategy?? throw new ArgumentNullException(nameof(writeStrategy));
       
    }
    /// <summary>
    ///处理方法
    /// </summary>
    /// <param name="plcConfig"></param>
    public virtual PLCReadModel Work(PLCConfig plcConfig)
    {
        GetPlcClient(plcConfig);
        AnalysisTrigger(plcConfig);
        return _readModel;
    }
    /// <summary>
    /// 获取到链接对象
    /// </summary>
    /// <returns></returns>
    private void GetPlcClient(PLCConfig plcConfig)
    {
        lock (_lock)
        {
            var ip = plcConfig.DeviceConfig.Ip;
            var client = PLCHelper.Get(ip);
            if (client == null)
            {
                client = _plcRepository.GetPlcClient(plcConfig);
                PLCHelper.Add(ip, client);
            }
            _plcClient = client;
        }
    }
    /// <summary>
    /// 解析触发器
    /// </summary>
    /// <param name="plcConfig"></param>
    private void AnalysisTrigger(PLCConfig plcConfig)
    {
        var triggers = plcConfig.TriggerDataConfigs;
        // 循环执行需要触发的操作
        foreach (var trigger in triggers)
        {
            // 读取plc比较位的值 如果通讯不成功  那么就会抛出异常 当前线程的任务就结束了
            var value = string.Empty;
            try
            {
                value = _plcRepository.ReadData(_plcClient, trigger.Register, (EnumDataType)trigger.DataType,
                    (EnumDataEncoding)(trigger.Encoding ?? 0), trigger.DataLength);
                _readModel.TriggerValue = value;
            }
            catch (Exception e)
            {
                // 如果第一次读取报错  那么就表示 通讯不成功 下面的也就没必执行了
                // 这边直接输出到控制台就好了  输出到日志的话  因为可能现场到时候  可能不会所有的设备都启动
                Log.Information(e,$"读取触发值失败:{trigger.Register}");
                break;
            }

            // 如果不满足触发条件  跳过  执行下一个触发
            if (!CompareTriggerCondition(trigger.JudgeType, trigger.JudgeValue, value, trigger.DataType))
            {
                continue;
            }

            //满足条件  就要执行对应的读取  写入 和判断操作了
            //1.先读取数据
            ReadDatas(plcConfig, trigger);
            //2.触发对应的操作
            var triggerResult = TriggerEvents(plcConfig, trigger);
            //3.根据触发操作的结果 来完成对应的操作 写入返回值给对应的寄存器
            WriteDatas(plcConfig, trigger, triggerResult);
            // 事件触发在外民进行统一的触发
        }
    }
   /// <summary>
   /// 触发条件的判断结果
   /// </summary>
   /// <param name="judgeType">判断类型</param>
   /// <param name="judgeValue">判断的值</param>
   /// <param name="value">plc值</param>
   /// <param name="dataType">plc值类型</param>
   /// <returns></returns>
   /// <exception cref="Exception"></exception>
    private bool CompareTriggerCondition(int judgeType, string judgeValue, string value,int dataType)
    {
        // 布尔值的时候 的判断 要单独拿出来
        var result = false;
        switch (judgeType)
        {
            case (int)EnumJudgeType.等于:
                if (dataType==(int)EnumDataType.布尔值)
                {
                    var boolValue = "false";
                    // bool外面只能输入0和1
                    if (value=="1")
                    {
                        boolValue = "true";
                    }
                    // 忽略大小写比较 bool值
                    result =  string.Equals(boolValue, judgeValue, StringComparison.OrdinalIgnoreCase);
                }
                else
                {
                    result = value == judgeValue;
                }
                break;
            case (int)EnumJudgeType.大于: // 字符串的时候 值判断字符的数量
                if (dataType==(int)EnumDataType.字符串)
                {
                    result = value.Length>judgeValue.Length;
                }
                else
                {
                    result = value.ToDecimal() > judgeValue.ToDecimal();
                }
                break;
            case (int)EnumJudgeType.大于等于:
                if (dataType == (int)EnumDataType.字符串)
                {
                    result = value.Length >= judgeValue.Length;
                }
                else
                {
                    result = value.ToDecimal() >= judgeValue.ToDecimal();
                }
                break;
            case (int)EnumJudgeType.小于:
                if (dataType == (int)EnumDataType.字符串)
                {
                    result = value.Length < judgeValue.Length;
                }
                else
                {
                    result = value.ToDecimal() < judgeValue.ToDecimal();
                }
                break;
            case (int)EnumJudgeType.小于等于:
                if (dataType == (int)EnumDataType.字符串)
                {
                    result = value.Length <= judgeValue.Length;
                }
                else
                {
                    result = value.ToDecimal() <= judgeValue.ToDecimal();
                }
                break;
            default: throw new Exception("没有判断类型对应的枚举的处理");
        }
        return result;
    }
   /// <summary>
   /// 读取触发器中的配置的数据组
   /// </summary>
   /// <param name="plcConfig"></param>
   /// <param name="trigger"></param>
   /// <exception cref="ArgumentNullException"></exception>
   private void ReadDatas(PLCConfig plcConfig,DtoDataConfig trigger)
   {
       // 数据组只要读一次就够了  要判断一下
       if (trigger.ReadGroup==null)
       {
           return;
       }
       var readGroup = trigger.ReadGroup.Value;
        if (_readGroupList.Contains(readGroup))
        {
            // 如果已经度过  那么就不读取了
            return;
        }
        else
        {
            // 没读取过就放入list 用来作为下次判断的依据
            _readGroupList.Add(readGroup);
        }
        // 这边要根据不同的读取类型来做策略了
        var readConfigList = plcConfig.ReadDataConfigs.Where(p => p.ReadGroup != null && p.ReadGroup == readGroup).ToList();
       var strategy  = _readStrategy.FirstOrDefault(p => p.ReadGroup==(EnumReadGroup)readGroup);
       if (strategy == null)
       {
           throw new ArgumentNullException(nameof(strategy));
       }
       // 读取数据
       strategy.Read(_plcClient,plcConfig,readConfigList,_readModel);
   }

    /// <summary>
    /// 执行所有的触发操作
    /// </summary>
    /// <param name="plcConfig"></param>
    /// <param name="trigger"></param>
    /// <returns></returns>
    private int TriggerEvents(PLCConfig plcConfig, DtoDataConfig trigger)
    {
        if (trigger.Trigger == null)
        {
            return (int)EnumWriteType.OK;
        }
        var strategy = _triggerStrategy.FirstOrDefault(p => (int)p.EnumTrigger == trigger.Trigger);
        if (strategy == null)
        {
            throw new InvalidOperationException(nameof(strategy));
        }
        var triggerResult = strategy.TriggerEvent(plcConfig,trigger, _readModel);
        return triggerResult;
    }
    /// <summary>
    /// 根据触发的操作的结果 来写入数据
    /// </summary>
    /// <param name="plcConfig"></param>
    /// <param name="trigger"></param>
    /// <param name="triggerResult"></param>
    /// <exception cref="Exception"></exception>
    private void WriteDatas(PLCConfig plcConfig,DtoDataConfig trigger,int triggerResult)
    {
        // 解析plc的写入配置  然后匹配到对应的值  然后写入 然后
        var writeType = trigger.WriteGroup;
        // 没有值就不执行写入操作
        if (writeType == null || writeType == 0)
        {
            return;
        }
        var writeConfig = plcConfig.DataConfigs.FirstOrDefault(p => p.WriteGroup == writeType);
        // 没有写入操作是有可能的
        if (writeConfig == null)
        {
            return;
        }

        var strategy = _writeStrategy.FirstOrDefault(p => p.WriteGroups.Contains((EnumWriteGroup)writeType));
        if (strategy == null)
        {
            throw new Exception("没有找到枚举值对应的写入的配置");
        }
        strategy.Write(_plcClient,plcConfig,writeConfig,triggerResult);
    }
}